/**
 * <copyright>
 *
 *  Copyright (c) 2009-2016 Thales Corporate Services S.A.S.
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-v2.0.
 *
 *  SPDX-License-Identifier: EPL-2.0
 * 
 *  Contributors:
 *      Thales Corporate Services S.A.S - initial API and implementation
 * 
 * </copyright>
 */

package org.eclipse.egf.emf.pattern.codegen;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egf.core.EGFCorePlugin;
import org.eclipse.egf.emf.pattern.codegen.CodegenPatternInitializer.CodegenJetPatternInitializer;
import org.eclipse.egf.emf.pattern.codegen.CodegenPatternInitializer.ContentProvider;
import org.eclipse.egf.emf.pattern.codegen.jdt.CodegenASTHelper;
import org.eclipse.egf.emf.pattern.codegen.jet.CodegenJetPatternHelper;
import org.eclipse.egf.emf.pattern.codegen.model.AbstractInfoComparator;
import org.eclipse.egf.emf.pattern.codegen.model.GIFPatternInfo;
import org.eclipse.egf.emf.pattern.codegen.model.JetPatternInfo;
import org.eclipse.egf.emf.pattern.codegen.model.JetSubPatternInfo;
import org.eclipse.egf.emf.pattern.codegen.model.PatternInfo;
import org.eclipse.egf.model.pattern.Pattern;
import org.eclipse.egf.pattern.extension.TemplateInitializer;
import org.eclipse.egf.pattern.jet.JetPreferences;
import org.eclipse.egf.pattern.jet.extension.JetPatternFactory;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.TransactionalEditingDomain;

/**
 * @author Matthieu Helleboid
 * 
 */
public class CodegenFcoreUtil {

	public static final String N = "\n"; //$NON-NLS-1$

	protected IProject codegenProject;

	protected IProject fcoreProject;

	protected Resource mdpstResource;

	protected Resource emfPatternBaseResource;

	protected Resource emfPatternResource;

	protected CodegenPatternHelper codegenPatternHelper;

	protected CodegenEGFHelper codegenEGFHelper;

	protected CodegenJetPatternHelper codegenJetPatternHelper;

	protected List<PatternInfo> patternInfos;

	protected CodegenVersionHelper codegenVersionHelper;

	protected class ClearCommand extends RecordingCommand {

		protected Exception exception;

		public ClearCommand(TransactionalEditingDomain domain) {
			super(domain);
		}

		@Override
		protected void doExecute() {
			try {
				emfPatternResource.getContents().clear();
			} catch (Exception e) {
				this.exception = e;
			}
		}

	}

	protected class CreateCommand extends RecordingCommand {

		protected Exception exception;

		protected IProgressMonitor monitor;

		public CreateCommand(TransactionalEditingDomain domain, IProgressMonitor monitor) {
			super(domain);
			this.monitor = monitor;
		}

		@Override
		protected void doExecute() {
			try {
				createPatterns(monitor);
				codegenEGFHelper.createOrchestration(emfPatternBaseResource, mdpstResource, patternInfos, monitor, codegenPatternHelper);
				codegenEGFHelper.createAllFactoryComponent();
				codegenEGFHelper.fixPatternElementOrder();
			} catch (Exception e) {
				this.exception = e;
			}
		}
	}

	public void createFcoreFile(IFile fcore, String emfCodegenEcoreProjectName, final IProgressMonitor monitor) throws Exception {
		final IOException[] ioExceptions = new IOException[1];

		fcoreProject = fcore.getProject();
		codegenProject = ResourcesPlugin.getWorkspace().getRoot().getProject(emfCodegenEcoreProjectName);
		if (!codegenProject.exists())
			throw new IllegalStateException("Cannot find project " + emfCodegenEcoreProjectName + " in workspace"); //$NON-NLS-1$ //$NON-NLS-2$

		if (!codegenProject.isOpen())
			throw new IllegalStateException("Project " + emfCodegenEcoreProjectName + " is closed"); //$NON-NLS-1$ //$NON-NLS-2$

		// Retrieve our editing domain
		TransactionalEditingDomain editingDomain = TransactionalEditingDomain.Registry.INSTANCE.getEditingDomain(EGFCorePlugin.EDITING_DOMAIN_ID);

		// Feed our URIConverter
		URI platformPluginURI = URI.createPlatformPluginURI(fcore.getFullPath().toString(), false);
		URI platformResourceURI = URI.createPlatformResourceURI(fcore.getFullPath().toString(), true);
		editingDomain.getResourceSet().getURIConverter().getURIMap().put(platformPluginURI, platformResourceURI);

		// Create a resource for this file.
		if (fcore.exists()) {
			emfPatternResource = editingDomain.getResourceSet().getResource(platformPluginURI, true);
			keepPreviousFcoreIdsAndOrder(fcore, editingDomain, platformPluginURI);
			clearFcore(editingDomain);
		} else
			emfPatternResource = editingDomain.getResourceSet().createResource(platformPluginURI);

		URI emfPatternBaseResourceURI = URI.createPlatformPluginURI("/org.eclipse.egf.emf.pattern.base/egf/EMF_Pattern_Base.fcore", true); //$NON-NLS-1$
		emfPatternBaseResource = editingDomain.getResourceSet().getResource(emfPatternBaseResourceURI, true);
		URI mdpstResourceURI = URI.createPlatformPluginURI("/org.eclipse.egf.pattern.ftask/egf/Model_driven_pattern_strategy_task.fcore", true); //$NON-NLS-1$
		mdpstResource = editingDomain.getResourceSet().getResource(mdpstResourceURI, true);

		createCodegenVersionHelper();
		createCodegenPatternHelper();
		createCodegenEGFHelper(); //called with the newly created resource
		createCodegenJetPattenHelper();
		createPatternInfos(monitor);
		computePatternInfoNames();
		computeSubPatternInfo(monitor);
		computePatternInfoNames();
		computeMethodsContent(monitor);
		computePatternInfoDescription();

		// Add factory component to the contents.
		CreateCommand createCommand = new CreateCommand(editingDomain, monitor);
		editingDomain.getCommandStack().execute(createCommand);
		if (createCommand.exception != null)
			throw createCommand.exception;

		// save fcore
		try {
			editingDomain.runExclusive(new Runnable() {

				public void run() {
					try {
						emfPatternResource.save(Collections.EMPTY_MAP);
					} catch (IOException ioe) {
						ioExceptions[0] = ioe;
					}
				}
			});
		} catch (InterruptedException ie) {
			return;
		}

		// Rethrow exception if any
		if (ioExceptions[0] != null) {
			throw ioExceptions[0];
		}

		replaceManifestVersion();

		return;
	}

	protected void replaceManifestVersion() throws CoreException, IOException {
		codegenVersionHelper.replaceManifestVersion();
	}

	protected void clearFcore(TransactionalEditingDomain editingDomain) throws Exception {
		ClearCommand clearCommand = new ClearCommand(editingDomain);
		editingDomain.getCommandStack().execute(clearCommand);
		if (clearCommand.exception != null)
			throw clearCommand.exception;
	}

	protected void keepPreviousFcoreIdsAndOrder(IFile fcore, TransactionalEditingDomain editingDomain, URI platformPluginURI) {
		// Try to keep xmi ids if fcore exists
		createCodegenEGFHelper(); //called with old resource
		codegenEGFHelper.populateXmiIds();
		codegenEGFHelper.populatePatternOrder();
	}

	protected void createCodegenPatternHelper() {
		codegenPatternHelper = new CodegenPatternHelper();
	}

	protected void createCodegenEGFHelper() {
		codegenEGFHelper = new CodegenEGFHelper(emfPatternResource, codegenEGFHelper);
	}

	public void createPatternInfos(IProgressMonitor monitor) throws Exception {
		patternInfos = new CodegenASTHelper().createPatternInfo(codegenProject, monitor);
		// sort list to have Model, Edit, Editor, Tests according to PartType
		// ordinal order
		Collections.sort(patternInfos, new AbstractInfoComparator());
	}

	public void computeSubPatternInfo(IProgressMonitor monitor) throws Exception {
		codegenJetPatternHelper.computeJetSubPatternInfo(patternInfos);
	}

	public void createCodegenJetPattenHelper() {
		codegenJetPatternHelper = new CodegenJetPatternHelper(codegenPatternHelper, codegenProject, emfPatternBaseResource, emfPatternResource);
	}

	public void createCodegenVersionHelper() {
		codegenVersionHelper = new CodegenVersionHelper(codegenProject, fcoreProject);
	}

	public void computePatternInfoNames() {
		new CodegenPatternNameResolver(codegenPatternHelper).computePatternName(patternInfos);
	}

	public void computePatternInfoDescription() throws Exception {
		new CodegenPatternDescriptionHandler(codegenProject).computeDescription(patternInfos, codegenVersionHelper);
	}

	public void computeMethodsContent(IProgressMonitor monitor) {
		for (PatternInfo patternInfo : patternInfos) {
			CodegenPatternMethodContentResolver codegenPatternMethodContentResolver = new CodegenPatternMethodContentResolver(codegenProject, codegenPatternHelper, emfPatternBaseResource, codegenJetPatternHelper);
			if (patternInfo instanceof JetPatternInfo) {
				JetPatternInfo jetPatternInfo = (JetPatternInfo) patternInfo;
				codegenPatternMethodContentResolver.computeMethodsContent(jetPatternInfo);
			} else if (patternInfo instanceof GIFPatternInfo) {
				GIFPatternInfo gifPatternInfo = (GIFPatternInfo) patternInfo;
				codegenPatternMethodContentResolver.computeMethodsContent(gifPatternInfo);
			} else if (patternInfo instanceof JetSubPatternInfo) {
				JetSubPatternInfo jetSubPatternInfo = (JetSubPatternInfo) patternInfo;
				codegenPatternMethodContentResolver.computeMethodsContent(jetSubPatternInfo);
			} else
				throw new IllegalStateException();

			monitor.worked(10);
		}
	}

	public void createPatterns(IProgressMonitor monitor) throws Exception {
		for (PatternInfo patternInfo : patternInfos) {
			Pattern pattern = getPatternFactory(patternInfo).createPattern();
			CodegenEngine codegenEngine = getCodeEngine(patternInfo);
			ContentProvider contentProvider = createContentProvider(patternInfo);
			TemplateInitializer initializer = getPatternTemplateInitializer(pattern, patternInfo, codegenEngine, contentProvider);
			initializer.initContent();

			monitor.worked(10);
		}
	}

	public TemplateInitializer getPatternTemplateInitializer(Pattern pattern, PatternInfo patternInfo, CodegenEngine codegenEngine, ContentProvider contentProvider) {
		return new CodegenJetPatternInitializer(fcoreProject, pattern, codegenEngine, contentProvider);
	}

	public ContentProvider createContentProvider(PatternInfo patternInfo) {
		return new ContentProvider(patternInfo, true);
	}

	public CodegenEngine getCodeEngine(PatternInfo patternInfo) {
		return new CodegenEngine(org.eclipse.egf.pattern.jet.Activator.getDefault().getPluginID(), fcoreProject, JetPreferences.getTemplateFileExtension());
	}

	public CodegenPatternFactory getPatternFactory(PatternInfo patternInfo) {
		return new CodegenPatternFactory(new JetPatternFactory(), emfPatternBaseResource, codegenEGFHelper, patternInfo, codegenPatternHelper);
	}
}
